package com.mxx.tomcat.verification;

import com.mxx.common.conv.JacksonConverter;
import com.mxx.common.exception.UnsupportedException;
import com.mxx.common.exception.ValidateException;
import com.mxx.common.utils.Strings;
import com.mxx.tomcat.ResponseWriter;
import com.mxx.tomcat.verification.annotation.Verify;
import com.mxx.tomcat.verification.annotation.WebVerification;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;

/**
 * @Author itzgyw
 * @Date 2020-03-30
 * @Time 16:19
 * @Description:
 */
@Slf4j
public abstract class AbstractValidateArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {

    protected ResponseWriter responseWriter;
    protected JacksonConverter jacksonConverter;
    protected Map<MethodParameter, Verify> verifyMap;
    protected final String VALIDATE = "validate";

    public AbstractValidateArgumentResolver(List<HttpMessageConverter<?>> converters, JacksonConverter jacksonConverter, ResponseWriter responseWriter) {
        super(converters);
        this.responseWriter = responseWriter;
        this.jacksonConverter = jacksonConverter;
        this.verifyMap = new HashMap<>();
    }

    abstract Map<String, String> getRequestParameterMap(NativeWebRequest nativeWebRequest, MethodParameter methodParameter, Verify.Type type) throws IOException, HttpMediaTypeNotSupportedException;


    public void validate(NativeWebRequest nativeWebRequest, MethodParameter methodParameter, Verify.Type type) throws Exception {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        Verify verify = this.verifyMap.get(methodParameter);
        if (verify != null) {
            Boolean validate = (Boolean) request.getAttribute(VALIDATE);
            if (validate == null || !validate) {
                Function<Object, Boolean> paramVerification = this.paramVerify(verify);
                Map<String, String> requestParameter = this.getRequestParameterMap(nativeWebRequest, methodParameter, type);
                for (String name : verify.value()) {
                    boolean flag = paramVerification.apply(requestParameter.get(name));
                    if (!flag) {
                        throw throwVerificationException(name, verify, nativeWebRequest);
                    }
                }
                request.setAttribute(VALIDATE, true);
            }
        }
    }


    public String getRequestParameterString(NativeWebRequest nativeWebRequest, MethodParameter methodParameter, Verify.Type type) throws IOException, HttpMediaTypeNotSupportedException {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        String requestString = (String) request.getAttribute(type.name());
        if (requestString == null) {
            Map<String, String> requestParameter = this.getRequestParameterMap(nativeWebRequest, methodParameter, type);
            requestString = jacksonConverter.writeValueAsString(requestParameter);
            request.setAttribute(type.name(), requestString);
        }
        return requestString;
    }


    protected ValidateException throwVerificationException(String name, Verify verify, NativeWebRequest nativeWebRequest) throws Exception {
        HttpServletResponse response = (HttpServletResponse) nativeWebRequest.getNativeResponse();
        String failureMessage = String.format("Validation of parameters failed : [%s cannot be %s]", name, verify.allowEmpty() ? "null" : "empty");
        responseWriter.writer(response, responseWriter.getFailureEntity(failureMessage));
        return new ValidateException(String.format("{uri:[%s].message:[%s]}", ((HttpServletRequest) nativeWebRequest.getNativeRequest()).getRequestURL(), failureMessage));
    }

    protected UnsupportedException throwUnsupportedException(String failureMessage, NativeWebRequest nativeWebRequest) throws Exception {
        HttpServletResponse response = (HttpServletResponse) nativeWebRequest.getNativeResponse();
        responseWriter.writer(response, responseWriter.getFailureEntity(failureMessage));
        return new UnsupportedException(String.format("{uri:[%s].message:[%s]}", ((HttpServletRequest) nativeWebRequest.getNativeRequest()).getRequestURL(), failureMessage));
    }


    public Verify getVerify(WebVerification webVerification, Verify.Type supportedType) {
        if (webVerification == null) {
            return null;
        }
        Verify[] verifies = webVerification.value();
        for (Verify verify : verifies) {
            if (verify.type().equals(supportedType)) {
                return verify;
            }
        }
        return null;
    }


    public boolean allDo(String nameArray[], String paramName) {
        return true;
    }

    protected Function<Object, Boolean> paramVerify(Verify verify) {
        return verify.allowEmpty() ? Strings::isNotNull : Strings::isNotEmpty;
    }


}
